07 — 全屏与自适应调整
介绍
我们的 canvas 目前有一个固定的分辨率 800x600。你并不一定需要让 WebGL 铺满整个屏幕,但如果你想要沉浸式体验,那可能更好。
首先,我们希望 canvas 占据所有可用空间。然后,我们需要确保在用户调整窗口大小后它仍然适配。最后,我们需要给用户一种体验全屏模式的方式。
准备工作
starter 文件里包含我们在上一课完成的内容。我们的立方体在中央,可以拖放来移动相机。
填满视口
要让 canvas 完美填满视口,不使用 sizes 变量里的固定数字,而是使用 window.innerWidth 和 window.innerHeight:
// ...
// Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
// ...
你可以看到 canvas 现在有了视口的宽和高。遗憾的是,有一圈白色的外边距和一个滚动条(如果你看不到滚动条,试着滚动一下)。
问题在于,浏览器都有各自的默认样式,比如标题更大、链接带下划线、段落之间有间距、页面带 padding。修复这个问题有很多方式,而且可能取决于你网站的其余部分。如果你还有其他内容,做这些的时候尽量不要破坏它们。
我们会保持简单,用 CSS 固定 canvas 的位置。
我们的 HTML 已经在加载 src/style.css 文件了:
<link rel="stylesheet" href="./style.css"> 你可以像平时一样写标准 CSS,页面会自动重新加载。
首先该做的好事是,用通配符 * 移除所有元素上任何类型的 margin 或 padding:
*
{
margin: 0;
padding: 0;
} 然后,我们可以用它的 webgl class 选中 canvas 并把它固定在左上角:
.webgl
{
position: fixed;
top: 0;
left: 0;
} 你不需要在 canvas 上指定 width 或 height,因为当你调用 renderer.setSize(...) 方法时,Three.js 已经在处理这些了。
这也是修复我们 canvas 上一个小问题的好机会。也许你注意到了在拖放时它有一个蓝色描边。这主要发生在最新版本的 Chrome 上。要修复它,我们只需给 .webgl 加上一个 outline: none;:
.webgl
{
position: fixed;
top: 0;
left: 0;
outline: none;
} 如果你想在触摸屏上也移除任何类型的滚动,你可以在 html 和 body 上加一个 overflow: hidden:
html,
body
{
overflow: hidden;
}
现在你可以尽情欣赏你的 WebGL 了。遗憾的是,如果你调整窗口大小,canvas 不会跟随。
我们需要处理自适应调整。
处理自适应调整
要调整 canvas 的大小,我们首先需要知道窗口何时被调整。为此,你可以监听 window 上的 resize 事件。
在 sizes 变量之后马上添加 resize 监听器:
window.addEventListener('resize', () =>
{
console.log('window has been resized')
}) 现在,当窗口缩放时会触发一个函数,我们需要更新代码里的几样东西。
首先,我们必须更新 sizes 变量:
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
}) 其次,通过修改它的 aspect 属性,我们必须更新 camera 的纵横比:
window.addEventListener('resize', () =>
{
// ...
// Update camera
camera.aspect = sizes.width / sizes.height
}) 当你修改像 aspect 这样的相机属性时,还需要用 camera.updateProjectionMatrix() 更新投影矩阵。我们以后会讲 matrix:
window.addEventListener('resize', () =>
{
// ...
camera.updateProjectionMatrix()
}) 最后,我们必须更新 renderer。更新渲染器会自动更新 canvas 的宽度和高度:
window.addEventListener('resize', () =>
{
// ...
// Update renderer
renderer.setSize(sizes.width, sizes.height)
}) 全部合在一起:
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
})
你可以随意缩放窗口,canvas 应该覆盖整个视口,没有任何滚动条或溢出。
处理像素比
你们中的一些人可能会看到一种模糊的渲染,以及边缘上像楼梯一样的锯齿影迹(叫做 aliasing 锯齿),但并非所有人都会。如果你看到了,那是因为你在一个像素比大于 1 的屏幕上测试。
像素比对应的是:软件层面的一个像素单位,对应屏幕上多少个物理像素。
一点历史
几年前,所有屏幕的像素比都是 1,一切都很好。但你凑近看屏幕时,能看到那些像素,这限制了图像能有多精细、字体能有多细。
在这方面做得最多的公司是苹果。苹果看到了机会,开始制造像素比为 2 的屏幕,叫做 retina(视网膜屏)。现在,很多制造商都在这样做,你甚至能看到像素比更高的屏幕。
虽然这对图像质量是件好事,但像素比 2 意味着要渲染多 4 倍的像素。而像素比 3 意味着多 9 倍的像素。
你猜怎么着?最高的像素比通常出现在最弱小的设备上——也就是手机上。
你的帧率就这样没了。
处理像素比
要获取屏幕像素比,你可以用 window.devicePixelRatio,而要更新渲染器的像素比,只需要调用 renderer.setPixelRatio(...)。
你可能很想直接把设备像素比传给这个方法,但在高像素比设备上你最终会碰到性能问题。
像素比大于 2 大多是营销噱头。你的眼睛几乎看不出 2 和 3 的区别,但它会造成性能问题,还更快掉电。你能做的是把像素比限制在 2。要做到这一点,可以用 Math.min():
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
有些技巧可以在像素比变化时得到通知,但这只涉及那些拥有多种像素比的多屏幕用户,而且他们在从一个屏幕切到另一个屏幕时通常会调整窗口大小。这就是为什么我们也会把这个方法加进 resize 回调:
window.addEventListener('resize', () =>
{
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
处理全屏
现在我们的 canvas 以正确的像素比占据了所有可用空间,是时候添加全屏支持了。
首先,我们需要决定什么操作会触发全屏模式。它可以是一个 HTML 按钮,但相反,我们打算使用双击。
当双击发生时,我们会切换全屏——意思是,如果窗口不在全屏,双击会启用全屏模式;如果窗口已经在全屏,双击会退出全屏模式。
首先,我们需要监听双击事件,我们可以用 dblclick 事件来做:
window.addEventListener('dblclick', () =>
{
console.log('double click')
}) 这个事件在大多数现代浏览器上都能工作,除了 Chrome Android:https://developer.mozilla.org/docs/Web/API/Element/dblclick_event
现在我们有了事件,还需要三样东西:
- 一种得知是否已经处于全屏的方式
- 一个进入全屏模式的方法
- 一个退出全屏模式的方法
要得知我们是否已经在全屏,我们可以用 document.fullscreenElement:
window.addEventListener('dblclick', () =>
{
if(!document.fullscreenElement)
{
console.log('go fullscreen')
}
else
{
console.log('leave fullscreen')
}
}) 请求全屏的方法与元素绑定。这是因为你可以选择让什么进入全屏。它可以是整个页面、任意 DOM 元素或 <canvas>。
我们会使用 <canvas>,并在它上面调用 requestFullscreen() 方法:
window.addEventListener('dblclick', () =>
{
if(!document.fullscreenElement)
{
canvas.requestFullscreen()
}
else
{
console.log('leave fullscreen')
}
}) 退出全屏模式的方法直接在 document 上可用:
window.addEventListener('dblclick', () =>
{
if(!document.fullscreenElement)
{
canvas.requestFullscreen()
}
else
{
document.exitFullscreen()
}
})
你可以通过在任意位置双击来测试切换全屏模式。
这个浏览器在正式支持像全屏这样简单的功能上一直拖拖拉拉,我们需要使用带前缀的版本才能让 document.fullscreenElement、canvas.requestFullscreen 和 document.exitFullscreen 正常工作:
window.addEventListener('dblclick', () =>
{
const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement
if(!fullscreenElement)
{
if(canvas.requestFullscreen)
{
canvas.requestFullscreen()
}
else if(canvas.webkitRequestFullscreen)
{
canvas.webkitRequestFullscreen()
}
}
else
{
if(document.exitFullscreen)
{
document.exitFullscreen()
}
else if(document.webkitExitFullscreen)
{
document.webkitExitFullscreen()
}
}
}) 在所有现代浏览器上,一切应该都能正常工作。